#!/usr/bin/env bash

# For the license, see the LICENSE file in the root directory.
#set -x

if [ "${SWTPM_TEST_IBMTSS2:-0}" -eq 0 ]; then
	echo "SWTPM_TEST_IBMTSS2 must be set to run this test."
	exit 77
fi

if ! type -p nvdefinespace startup &>/dev/null; then
	PREFIX=tss
	if ! type -p ${PREFIX}nvdefinespace ${PREFIX}startup; then
		echo "Could not find TPM2 tools (e.g., (tss)startup, (tss)nvdefinespace) in PATH."
		exit 77
	fi
fi
TOOLSPATH=$(dirname "$(type -P "${PREFIX}startup")")

ROOT=${abs_top_builddir:-$(dirname "$0")/..}
TESTDIR=${abs_top_testdir:-$(dirname "$0")}

TPMDIR="$(mktemp -d)" || exit 1
PID_FILE=$TPMDIR/swtpm.pid
SOCK_PATH=$TPMDIR/sock
LOGFILE=$TPMDIR/logfile
LOGFILE2=$TPMDIR/logfile2
TMPFILE=$TPMDIR/tmpfile
BINFILE=$TPMDIR/binfile
SIGFILE=$TPMDIR/sigfile
STATEFILE=${STATEFILE:-$TPMDIR/state}

STORE_PARAM="dir=$TPMDIR"
if [ "${SWTPM_TEST_LINEAR_FILE:-0}" -ne 0 ]; then
	echo "Testing with linear file backend ($STATEFILE)"
	STORE_PARAM="backend-uri=file://$STATEFILE"
fi

source "${TESTDIR}/test_common"
source "${TESTDIR}/common"
skip_test_no_tpm20 "${SWTPM_EXE}"


trap "cleanup" SIGTERM EXIT

function cleanup()
{
	rm -rf "$TPMDIR"
	# remove files from tss tools
	rm -f h01*.bin nvp*.bin
	if [ -n "$PID" ]; then
		kill_quiet -SIGTERM "$PID" 2>/dev/null
	fi
}

# Fill up the NVRAM space with 2048 bit signing keys and then an NVRAM area that
# fills it up to the last byte. We want to make sure that the OBJECTs that the
# RSA keys are creating in NVRAM can be loaded into the NVRAM again when the size
# of the OBJECT increases when for example the size of the RSA keys increases.
# This may force us to increase the NVRAM memory space in libtpms then.
function fillup_nvram()
{
	local create="$1"
	local check="$2"

	local i sz

	if [ "$create" -eq 1 ]; then
		# Fill up the NVRAM space with RSA 2048 keys;
		# exactly 65 have to fit
		if ! "${TOOLSPATH}/${PREFIX}createprimary" -hi o -si > "$TMPFILE"; then
			echo "Error: createprimary failed."
			exit 1
		fi
		if ! grep -q 80000000 "$TMPFILE"; then
			echo "Error: createprimary did not result in expected handle 80000000"
			exit 1
		fi
		for ((i = 0x81000000; i < 0x81000100; i++)); do
			"${TOOLSPATH}/${PREFIX}evictcontrol" \
				-hi o \
				-ho 80000000 \
				-hp "$(printf "%x" "$i")" &>"$TMPFILE" || break
		done
		"${TOOLSPATH}/${PREFIX}getcapability" -cap 1 -pr 81000000 -pc 80 > "$TMPFILE"
		# We need know we need to see '65 Handles' for state created with
		# libtpms-0.6.0 and 128kb NVRAM size
		if ! grep -i "65 Handles" "$TMPFILE"; then
			echo "Error: Did not find '65 Handles' keyword in output"
			cat "$TMPFILE"
			exit 1
		fi

		# Fill up the rest of the NVRAM with a single NVRAM index whose size
		# we now have to find;
		# for reference: libtpms v0.6.0 allowed 236 bytes
		for ((sz = 0; ; sz++)); do
			"${TOOLSPATH}/${PREFIX}nvdefinespace" \
				-hi o \
				-ha 01000000 \
				-sz "${sz}" > "${TMPFILE}" || break
			# this worked, so lets remove it and try the next size
			#echo "NVRAM space of size $sz could be created"
			"${TOOLSPATH}/${PREFIX}nvundefinespace" \
				-hi o \
				-ha 01000000 > "${TMPFILE}"
		done
		if [ "$sz" -gt 0 ]; then
			sz=$((sz - 1))
			echo "Creating final space of size ${sz}"
			if ! "${TOOLSPATH}/${PREFIX}nvdefinespace" \
				-hi o \
				-ha 01000000 \
				-sz ${sz} > "${TMPFILE}";
			then
				echo "Error: Could not create final NVRAM space."
				cat "${TMPFILE}"
				exit 1
			fi
		fi
		if [ $sz -eq 0 ]; then
			echo "Error: NVRAM space could not be created at all; not enough space!"
			exit 1
		elif [ $sz -lt 236 ]; then
			echo "Error: Insufficient NVRAM memory. Needed to create an NVRAM index with size 236 bytes."
			exit 1
		elif [ $sz -gt 236 ]; then
			echo "Error: The NVRAM index is too large. Only needed 236 bytes but got $sz bytes."
			exit 1
		else
			echo "The NVRAM index is exactly of the right size (236 bytes)."
		fi

		echo -n "123" > "$BINFILE"
		if ! "${TOOLSPATH}/${PREFIX}sign" \
			-hk 81000000 \
			-if "${BINFILE}" \
			-os "${SIGFILE}" > "$TMPFILE";
		then
			echo "Error: Could not create signature."
			cat "$TMPFILE"
			exit 1
		fi
	fi

	if [ "$check" -eq 1 ]; then
		"${TOOLSPATH}/${PREFIX}getcapability" -cap 1 -pr 81000000 -pc 80 > "$TMPFILE"
		# We need know we need to see '65 Handles' for state created with
		# libtpms-0.6.0 and 128kb NVRAM size
		if ! grep -i "65 Handles" "$TMPFILE"; then
			echo "Error: Did not find '65 Handles' keyword in output"
			cat "$TMPFILE"
			exit 1
		fi

		printf "Verifying signature with all the persisted keys\n"
		echo -n "123" > "$BINFILE"
		for ((i = 0x81000000; i < 0x81000040; i++)); do
			if ! "${TOOLSPATH}/${PREFIX}verifysignature" \
				-hk "$(printf "%x" "$i")" \
				-is "${SIGFILE}" \
				-if "${BINFILE}" > "$TMPFILE";
			then
				echo "Verifying signature failed for handle $(printf "%x" "$i")."
				exit 1
			fi
		done
	fi
}

export TPM_SERVER_TYPE=raw
export TPM_SERVER_NAME=127.0.0.1
export TPM_INTERFACE_TYPE=socsim
export TPM_COMMAND_PORT=${TPM_COMMAND_PORT:-65460}
export TPM_DATA_DIR=$TPMDIR
export TPM_SESSION_ENCKEY="807e2bfe898ddaed8fa6310e716a24dc" # for sessions

$SWTPM_EXE socket \
	--server "port=${TPM_COMMAND_PORT}" \
	--tpmstate "$STORE_PARAM" \
	--pid "file=$PID_FILE" \
	--ctrl "type=unixio,path=$SOCK_PATH" \
	--log "file=$LOGFILE,level=20" \
	--tpm2 \
	${SWTPM_TEST_SECCOMP_OPT:+${SWTPM_TEST_SECCOMP_OPT}} &

if wait_for_file "$PID_FILE" 3; then
	echo "Error: (1) Socket TPM did not write pidfile."
	exit 1
fi

PID="$(cat "$PID_FILE")"

# Send TPM_Init
if ! act=$($SWTPM_IOCTL --unix "$SOCK_PATH" -i 2>&1); then
	echo "Error: $SWTPM_IOCTL CMD_INIT failed: $act"
	exit 1
fi

if ! "${TOOLSPATH}/${PREFIX}startup" -c; then
	echo "Error: tpm_startup clear failed."
	exit 1
fi

fillup_nvram 1 1

# Send Shutdown
if ! act=$($SWTPM_IOCTL --unix "$SOCK_PATH" -s 2>&1); then
	echo "Error: $SWTPM_IOCTL CMD_SHUTDOWN failed: $act"
	exit 1
fi

if wait_process_gone "${PID}" 4; then
	echo "Error: swtpm did not shut down"
	exit 1
fi

echo "============================" >> "$LOGFILE"

echo "TPM was shut down"

# Store this state for later usage; use a really old version of libtpms: 0.6.0
#cp $TPMDIR/tpm2-00.permall ${TESTDIR}/data/tpm2state5;
#cp $SIGFILE ${TESTDIR}/data/tpm2state5/signature.bin

#################################################################
# Run TPM2 with the created state and verify it's the same

$SWTPM_EXE socket \
	--server "port=${TPM_COMMAND_PORT}" \
	--tpmstate "$STORE_PARAM" \
	--pid "file=$PID_FILE" \
	--ctrl "type=unixio,path=$SOCK_PATH" \
	--log "file=$LOGFILE,level=20" \
	--tpm2 \
	${SWTPM_TEST_SECCOMP_OPT:+${SWTPM_TEST_SECCOMP_OPT}} &

if wait_for_file "$PID_FILE" 3; then
	echo "Error: (2) Socket TPM did not write pidfile."
	exit 1
fi

echo "TPM re-started"

PID="$(cat "$PID_FILE")"

# Send TPM_Init
if ! act=$($SWTPM_IOCTL --unix "$SOCK_PATH" -i 2>&1); then
	echo "Error: $SWTPM_IOCTL CMD_INIT failed: $act"
	cat "$LOGFILE"
	exit 1
fi

if ! "${TOOLSPATH}/${PREFIX}startup" -c; then
	echo "Error: tpm_startup clear failed."
	cat "$LOGFILE"
	exit 1
fi

fillup_nvram 0 1

if ! "${TOOLSPATH}/${PREFIX}shutdown" -c; then
	echo "Error: tpm_shutdown clear failed."
	cat "$LOGFILE"
	exit 1
fi

# Send Shutdown
if ! act=$($SWTPM_IOCTL --unix "$SOCK_PATH" -s 2>&1); then
	echo "Error: $SWTPM_IOCTL CMD_SHUTDOWN failed: $act"
	exit 1
fi

echo "============================" >> "$LOGFILE"

echo "TPM was shut down"

#################################################################
# Run TPM2 with previously saved state and verify it's the same

if [ "${SWTPM_TEST_LINEAR_FILE:-0}" -ne 0 ]; then
	echo "Test 1 OK (skipped last with linear file)"
	exit 0
fi

rm -f "$TPMDIR"/*
cp -f "${TESTDIR}/data/tpm2state5/tpm2-00.permall" "$TPMDIR/tpm2-00.permall"
cp "${TESTDIR}/data/tpm2state5/signature.bin" "$SIGFILE"

$SWTPM_EXE socket \
	--server "port=${TPM_COMMAND_PORT}" \
	--tpmstate "$STORE_PARAM,lock" \
	--pid "file=$PID_FILE" \
	--ctrl "type=unixio,path=$SOCK_PATH" \
	--log "file=$LOGFILE,level=20" \
	--tpm2 \
	${SWTPM_TEST_SECCOMP_OPT:+${SWTPM_TEST_SECCOMP_OPT}} &

if wait_for_file "$PID_FILE" 3; then
	echo "Error: (3) Socket TPM did not write pidfile."
	exit 1
fi

echo "TPM started with previously generated state"

PID="$(cat "$PID_FILE")"

# Send TPM_Init
if ! act=$($SWTPM_IOCTL --unix "$SOCK_PATH" -i 2>&1); then
	echo "Error: $SWTPM_IOCTL CMD_INIT failed: $act"
	exit 1
fi

if ! "${TOOLSPATH}/${PREFIX}startup" -c; then
	echo "Error: tpm_startup clear failed."
	cat "$LOGFILE"
	exit 1
fi

fillup_nvram 0 1

if ! "${TOOLSPATH}/${PREFIX}shutdown" -c; then
	echo "Error: tpm_shutdown clear failed."
	cat "$LOGFILE"
	exit 1
fi

# Start another swtpm that must fail to start due to locked storage
# The not-need-init flag causes the failure.
if $SWTPM_EXE socket \
	--tpmstate "$STORE_PARAM,lock" \
	--log "file=${LOGFILE2},level=1" \
	--flags not-need-init \
	--tpm2 \
	${SWTPM_TEST_SECCOMP_OPT:+${SWTPM_TEST_SECCOMP_OPT}}; then
	echo "Error: Starting 2nd instance on locked storage should have failed"
	exit 1
fi
if ! grep -q "Resource temporarily unavailable" "${LOGFILE2}"; then
	echo "Error: Expected error 'Resource temporarily unavailable' in logfile"
	cat "${LOGFILE2}"
	exit 1
fi

# Send Shutdown
if ! act=$($SWTPM_IOCTL --unix "$SOCK_PATH" -s 2>&1); then
	echo "Error: $SWTPM_IOCTL CMD_SHUTDOWN failed: $act"
	exit 1
fi

echo "Test 1 OK"
